#include <stdio.h>

#include "esp_log.h"
#include "esp_timer.h"
#include "esp_adc_cal.h"
#include "esp_pm.h"
#include "esp_heap_caps.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "nmea.h"
#include "gpgll.h"
#include "gpgga.h"
#include "gprmc.h"
#include "gpgsa.h"
#include "gpvtg.h"
#include "gptxt.h"
#include "gpgsv.h"

#include "lvgl.h"
#include "lvgl_gui.h"

#include "driver/i2c.h"
#include "driver/adc.h"
#include "driver/ledc.h"
#include "driver/uart.h"

#include "iot_button.h"

#include "page/home.h"
#include "page/list.h"
#include "page/light.h"
#include "page/clock.h"
#include "page/netconfig.h"

#define UART_NUM UART_NUM_1
#define UART_RX_PIN 4
#define UART_TX_PIN 3
#define UART_RX_BUF_SIZE (1024)

TaskHandle_t xMainTask;
SemaphoreHandle_t xGuiSemaphore;

esp_adc_cal_characteristics_t *adc_chars;

// app定义 ===================
home_app_t home_app;
list_app_t list_app;
light_app_t light_app;
clock_app_t clock_app;
netconfig_app_t netconfig_app;

app_t curr_page = HOME_APP;
// ==========================

static const char *TAG = "mainc";

static void user_ledc_init(void)
{
    // Prepare and then apply the LEDC PWM timer configuration
    ledc_timer_config_t ledc_timer = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .timer_num = LEDC_CHANNEL_0,
        .duty_resolution = LEDC_TIMER_13_BIT,
        .freq_hz = 5000, // Set output frequency at 5 kHz
        .clk_cfg = LEDC_AUTO_CLK};
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

    // Prepare and then apply the LEDC PWM channel configuration
    ledc_channel_config_t ledc_channel = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .channel = LEDC_CHANNEL_0,
        .timer_sel = LEDC_TIMER_0,
        .intr_type = LEDC_INTR_DISABLE,
        .gpio_num = PIN_NUM_BK_LIGHT,
        .duty = 0, // Set duty to 0%
        .hpoint = 0};
    ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}

static void print_char_val_type(esp_adc_cal_value_t val_type)
{
    if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP)
    {
        ESP_LOGI(TAG, "Characterized using Two Point Value");
    }
    else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF)
    {
        ESP_LOGI(TAG, "Characterized using eFuse Vref");
    }
    else
    {
        ESP_LOGI(TAG, "Characterized using Default Vref");
    }
}

static void uart_setup()
{
    uart_config_t uart_config = {
        .baud_rate = 9600,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE};
    ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_TX_PIN, UART_RX_PIN,
                                 UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM, UART_RX_BUF_SIZE * 2, 0, 0, NULL, 0));

    char *set_bd_str = "$PCAS04,3*1A\r\n";
    uart_write_bytes(UART_NUM, (const char *)set_bd_str, strlen(set_bd_str));

    char *set_baud_str = "$PCAS01,5*19\r\n";
    uart_write_bytes(UART_NUM, (const char *)set_baud_str, strlen(set_baud_str));

    char *set_speed_str = "$PCAS02,500*1A\r\n";
    uart_write_bytes(UART_NUM, (const char *)set_speed_str, strlen(set_speed_str));

    char *set_mode_str = "$PCAS11,2*1F\r\n";
    uart_write_bytes(UART_NUM, (const char *)set_mode_str, strlen(set_mode_str));

    uart_wait_tx_done(UART_NUM, 100);

    uart_driver_delete(UART_NUM);
    uart_config.baud_rate = 115200;
    ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
    // ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_TX_PIN, UART_RX_PIN,
    //                              UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM, UART_RX_BUF_SIZE * 2, 0, 0, NULL, 0));
}

static void read_and_parse_nmea()
{
    // Configure a temporary buffer for the incoming data
    char *buffer = (char *)malloc(UART_RX_BUF_SIZE + 1);
    char fmt_buf[32];
    size_t total_bytes = 0;

    setenv("TZ", "CST-8", 1);
    tzset();

    while (1)
    {

        // Read data from the UART
        int read_bytes = uart_read_bytes(UART_NUM,
                                         (uint8_t *)buffer + total_bytes,
                                         UART_RX_BUF_SIZE - total_bytes, pdMS_TO_TICKS(500));
        if (read_bytes <= 0)
        {
            continue;
        }

        nmea_s *data;
        total_bytes += read_bytes;

        /* find start (a dollar sign) */
        char *start = memchr(buffer, '$', total_bytes);
        if (start == NULL)
        {
            total_bytes = 0;
            continue;
        }

        /* find end of line */
        char *end = memchr(start, '\r', total_bytes - (start - buffer));
        if (NULL == end || '\n' != *(++end))
        {
            continue;
        }
        end[-1] = NMEA_END_CHAR_1;
        end[0] = NMEA_END_CHAR_2;

        /* handle data */
        data = nmea_parse(start, end - start + 1, 0);
        if (data == NULL)
        {
            // printf("Failed to parse the sentence!\n");
            // printf("  Type: %.5s (%d)\n", start + 1, nmea_get_type(start));
        }
        else
        {
            if (data->errors != 0)
            {
                printf("WARN: The sentence struct contains parse errors!\n");
            }

            if (NMEA_GPGGA == data->type)
            {
                // printf("GPGGA sentence\n");
                nmea_gpgga_s *gpgga = (nmea_gpgga_s *)data;
                // printf("Number of satellites: %d\n", gpgga->n_satellites);
                // printf("Altitude: %f %c\n", gpgga->altitude,
                //        gpgga->altitude_unit);

                if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
                {
                    // ESP_LOGW(TAG, "get sem 3");
                    if (curr_page == HOME_APP)
                    {
                        lv_label_set_text_fmt(home_app.label_alt, "%.2f %c", gpgga->altitude, gpgga->altitude_unit);
                        lv_label_set_text_fmt(home_app.label_satellite_num, LV_SYMBOL_GPS "%d", gpgga->n_satellites);
                        switch (gpgga->position_fix)
                        {
                        case 0:
                            lv_label_set_text(home_app.label_gps_status, "Unavailable");
                            break;
                        case 1:
                            lv_label_set_text(home_app.label_gps_status, "Valid");
                            break;
                        case 6:
                            lv_label_set_text(home_app.label_gps_status, "Estimate");
                            break;
                        default:
                            lv_label_set_text(home_app.label_gps_status, "Error");
                            break;
                        }
                    }

                    xSemaphoreGive(xGuiSemaphore);
                    // ESP_LOGW(TAG, "give sem 3");
                }
            }

            // if (NMEA_GPGLL == data->type)
            // {
            //     printf("GPGLL sentence\n");
            //     nmea_gpgll_s *pos = (nmea_gpgll_s *)data;
            //     printf("Longitude:\n");
            //     printf("  Degrees: %d\n", pos->longitude.degrees);
            //     printf("  Minutes: %f\n", pos->longitude.minutes);
            //     printf("  Cardinal: %c\n", (char)pos->longitude.cardinal);
            //     printf("Latitude:\n");
            //     printf("  Degrees: %d\n", pos->latitude.degrees);
            //     printf("  Minutes: %f\n", pos->latitude.minutes);
            //     printf("  Cardinal: %c\n", (char)pos->latitude.cardinal);
            //     strftime(fmt_buf, sizeof(fmt_buf), "%H:%M:%S", &pos->time);
            //     printf("Time: %s\n", fmt_buf);
            // }

            if (NMEA_GPRMC == data->type)
            {
                // printf("GPRMC sentence\n");
                nmea_gprmc_s *pos = (nmea_gprmc_s *)data;
                // printf("Longitude:\n");
                // printf("  Degrees: %d\n", pos->longitude.degrees);
                // printf("  Minutes: %f\n", pos->longitude.minutes);
                // printf("  Cardinal: %c\n", (char)pos->longitude.cardinal);

                // printf("Latitude:\n");
                // printf("  Degrees: %d\n", pos->latitude.degrees);
                // printf("  Minutes: %f\n", pos->latitude.minutes);
                // printf("  Cardinal: %c\n", (char)pos->latitude.cardinal);

                time_t now;
                struct tm timeinfo;
                now = mktime(&pos->date_time);
                now += 28800;
                // 时间补偿
                // now += 2;
                localtime_r(&now, &timeinfo);

                // printf("Date & Time: %s\n", fmt_buf);
                if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
                {
                    // ESP_LOGW(TAG, "get sem 1");
                    if (curr_page == HOME_APP)
                    {
                        strftime(fmt_buf, sizeof(fmt_buf), "  %Y-%m-%d %T", &timeinfo);
                        lv_label_set_text(home_app.label_timer, fmt_buf);
                        lv_label_set_text_fmt(home_app.label_ne, "%c/%c", (char)pos->latitude.cardinal, (char)pos->longitude.cardinal);
                        lv_label_set_text_fmt(home_app.label_coordinate, "%f,%f", (pos->longitude.degrees + pos->longitude.minutes / 60), (pos->latitude.degrees + pos->latitude.minutes / 60));
                    }
                    else if (curr_page == CLOCK_APP)
                    {
                        strftime(fmt_buf, sizeof(fmt_buf), "  %Y-%m-%d\n%T", &timeinfo);
                        lv_label_set_text(clock_app.label, fmt_buf);
                    }

                    xSemaphoreGive(xGuiSemaphore);
                    // ESP_LOGW(TAG, "give sem 1");
                }

                // printf("Speed, in Knots: %f\n", pos->gndspd_knots);
                // printf("Track, in degrees: %f\n", pos->track_deg);c
                // printf("Magnetic Variation:\n");
                // printf("  Degrees: %f\n", pos->magvar_deg);
                // printf("  Cardinal: %c\n", (char)pos->magvar_cardinal);
                // double adjusted_course = pos->track_deg;
                // if (NMEA_CARDINAL_DIR_EAST == pos->magvar_cardinal)
                // {
                //     adjusted_course -= pos->magvar_deg;
                // }
                // else if (NMEA_CARDINAL_DIR_WEST == pos->magvar_cardinal)
                // {
                //     adjusted_course += pos->magvar_deg;
                // }
                // else
                // {
                //     printf("Invalid Magnetic Variation Direction!\n");
                // }

                // printf("Adjusted Track (heading): %f\n", adjusted_course);
            }

            // if (NMEA_GPGSA == data->type)
            // {
            //     nmea_gpgsa_s *gpgsa = (nmea_gpgsa_s *)data;

            //     printf("GPGSA Sentence:\n");
            //     printf("  Mode: %c\n", gpgsa->mode);
            //     printf("  Fix:  %d\n", gpgsa->fixtype);
            //     printf("  PDOP: %.2lf\n", gpgsa->pdop);
            //     printf("  HDOP: %.2lf\n", gpgsa->hdop);
            //     printf("  VDOP: %.2lf\n", gpgsa->vdop);
            // }

            // if (NMEA_GPGSV == data->type)
            // {
            //     nmea_gpgsv_s *gpgsv = (nmea_gpgsv_s *)data;

            //     printf("GPGSV Sentence:\n");
            //     printf("  Num: %d\n", gpgsv->sentences);
            //     printf("  ID:  %d\n", gpgsv->sentence_number);
            //     printf("  SV:  %d\n", gpgsv->satellites);
            //     printf("  #1:  %d %d %d %d\n", gpgsv->sat[0].prn, gpgsv->sat[0].elevation, gpgsv->sat[0].azimuth, gpgsv->sat[0].snr);
            //     printf("  #2:  %d %d %d %d\n", gpgsv->sat[1].prn, gpgsv->sat[1].elevation, gpgsv->sat[1].azimuth, gpgsv->sat[1].snr);
            //     printf("  #3:  %d %d %d %d\n", gpgsv->sat[2].prn, gpgsv->sat[2].elevation, gpgsv->sat[2].azimuth, gpgsv->sat[2].snr);
            //     printf("  #4:  %d %d %d %d\n", gpgsv->sat[3].prn, gpgsv->sat[3].elevation, gpgsv->sat[3].azimuth, gpgsv->sat[3].snr);
            // }

            // if (NMEA_GPTXT == data->type)
            // {
            //     nmea_gptxt_s *gptxt = (nmea_gptxt_s *)data;

            //     printf("GPTXT Sentence:\n");
            //     printf("  ID: %d %d %d\n", gptxt->id_00, gptxt->id_01, gptxt->id_02);
            //     printf("  %s\n", gptxt->text);
            // }

            if (NMEA_GPVTG == data->type)
            {
                nmea_gpvtg_s *gpvtg = (nmea_gpvtg_s *)data;

                // printf("GPVTG Sentence:\n");
                // printf("  Track [deg]:   %.2lf\n", gpvtg->track_deg);
                // printf("  Speed [kmph]:  %.2lf\n", gpvtg->gndspd_kmph);
                // printf("  Speed [knots]: %.2lf\n", gpvtg->gndspd_knots);

                if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
                {
                    // ESP_LOGW(TAG, "get sem 2");
                    if (curr_page == HOME_APP)
                    {
                        if (gpvtg->track_deg >= 0 && gpvtg->track_deg < 90)
                        {
                            lv_label_set_text(home_app.label_direction, "东北");
                        }
                        else if (gpvtg->track_deg >= 90 && gpvtg->track_deg < 180)
                        {
                            lv_label_set_text(home_app.label_direction, "东南");
                        }
                        else if (gpvtg->track_deg >= 180 && gpvtg->track_deg < 270)
                        {
                            lv_label_set_text(home_app.label_direction, "西南");
                        }
                        else if (gpvtg->track_deg >= 270 && gpvtg->track_deg < 360)
                        {
                            lv_label_set_text(home_app.label_direction, "西北");
                        }
                        else
                        {
                            lv_label_set_text(home_app.label_direction, "北");
                        }

                        lv_label_set_text_fmt(home_app.label_speed, "%.2lf KM/H\n%.2lf deg", gpvtg->gndspd_kmph, gpvtg->track_deg);
                    }

                    xSemaphoreGive(xGuiSemaphore);
                    // ESP_LOGW(TAG, "give sem 2");
                }
            }

            nmea_free(data);
        }

        /* buffer empty? */
        if (end == buffer + total_bytes)
        {
            total_bytes = 0;
            continue;
        }

        /* copy rest of buffer to beginning */
        if (buffer != memmove(buffer, end, total_bytes - (end - buffer)))
        {
            total_bytes = 0;
            continue;
        }

        total_bytes -= end - buffer;
    }
    free(buffer);
}

void user_init_adc()
{
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_8, ADC_ATTEN_DB_11);

    adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, adc_chars);
    print_char_val_type(val_type);
}

static void dump_haps()
{
    while (1)
    {
        size_t size = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
        size_t size_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
        ESP_LOGW(TAG, "%d, %d", size, size_internal);

        // char pcWriteBuffer[256];
        // vTaskList(pcWriteBuffer);
        // ESP_LOGE(TAG, "%s", pcWriteBuffer);
        // vTaskGetRunTimeStats(pcWriteBuffer);
        // ESP_LOGE(TAG, "%s", pcWriteBuffer);

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    user_ledc_init();
    user_init_adc();

    xTaskCreatePinnedToCore(guiTask, "gui", 1024 * 4, NULL, 5, NULL, 1);

    uart_setup();
    xTaskCreate(read_and_parse_nmea, "read_and_parse_nmea", 1024 * 3, NULL, 5, NULL);

    // StaticTask_t xTaskBuffer;
    // StackType_t *xStack = heap_caps_calloc(1024 * 4, sizeof(StackType_t), MALLOC_CAP_SPIRAM);
    // xTaskCreateStatic(dump_haps, "dump_haps", 1024 * 4, NULL, 5, xStack, &xTaskBuffer);
}
